<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
<script>
const oldVnode = {
  type: 'div',
  children: [
    {type: 'p', children: '1', key: 1},
    {type: 'div', children: '2', key: 2},
    {type: 'span', children: 'hello', key: 3}
  ]
}

const newVnode = {
  type: 'div',
  children: [
    {type: 'h1', children: 'h1', key: 4}, //增加新元素
    {type: 'span', children: 'word', key: 3},
    {type: 'div', children: '4', key: 2},
    {type: 'p', children: '3', key: 1},
  ]
}

function createRenderer(options) {
  
  const {createElement, setElementText, insert, patchProps, unmount} = options

  function render(vnode, container) {
    console.log(vnode,'vnode')
    if(vnode) {
      path(container._vnode, vnode, container)
    } else {
      if(container._vnode) {
        unmount(container._vnode)
      }
    }
    container._vnode = vnode
  }

  function path(n1, n2, container, anchor) {
    if(n1 && n1.type !== n2.type) {
      unmount(n1)
      n1 = null
    }
    const type = typeof n2.type
    if(type === 'string') {
      if(!n1) {
        mountElement(n2, container, anchor)
      } else {
        patchElement(n1, n2)
      }
    } else if(type === 'Text') {
      //挂载文本节点
    }else if(type === 'Fragment') {
      //
    }
  }

  function mountElement(vnode, container, anchor) {
    const {type, props, children} = vnode
    const el = vnode.el = createElement(type)
    if(typeof children === 'string') {//挂载子节点
      setElementText(el, children)
    } else if(Array.isArray(children)) {
      children.forEach((c)=> {
        path(null, c, el, anchor)
      })
    }
    if(props) { //挂载属性
      for(key in props) {
        patchProps(el, key, null, props[key])
      }
    }
    insert(el, container, anchor)
  }

  function patchElement(n1, n2) {
    const el = n2.el = n1.el
    const oldProps = n1.props || {}
    const newProps = n2.props || {}
     //1.更新属性
    for(const key in oldProps) {
      console.log(key,'oldProps')
      if(!(key in newProps)) {
        console.log(key,'key')
        patchProps(el, key, oldProps[key], null)
      }
    }
    for(const key in newProps) {
      if(newProps[key] !== oldProps[key]) {
        patchProps(el, key, oldProps[key], newProps[key])
      }
    }
    //2.更新子节点
    patchChildren(n1, n2, el)
  }

  function patchChildren(n1, n2,container) {
    const {children: newChildren} = n2
    const {children: oldChildren} = n1
    if(typeof newChildren === 'string') {
      if(Array.isArray(oldChildren)) {
        oldChildren.forEach(c=> unmount(c))
      }
      setElementText(container, newChildren)
    } else if(Array.isArray(newChildren)) {
      if(Array.isArray(oldChildren)) {
        const newLen = newChildren.length
        const oldLen = oldChildren.length
        let lastIndex = 0
          for(let i = 0; i < newLen; i++) {
            const newVnode = newChildren[i]
            let  find =  false
            for(let j = 0; j < oldLen; j++) {
              const oldVnode = oldChildren[j]
              if(newVnode.key === oldVnode.key) {
                find = true
                path(oldVnode, newVnode, container)
                if(j < lastIndex) {
                  //先获取newVnode的前一个 vnode, 即prevVnode
                  const prevVnode = newChildren[i - 1]
                  //如果prevVnode 不存在，则说明当前 newVnode 是第一个节点，它不需要移动
                  if(prevVnode) {
                    const anchor = prevVnode.el.nextSibling
                    console.log(newVnode.key,prevVnode.el)
                    insert(newVnode.el, container, anchor)
                  }
                } else {
                  lastIndex = j
                }
                break
              }
            }
            if(!find) {
              let anchor = null
              const prevVnode = newChildren[i - 1]
              if(prevVnode) {
                //如果有前一个vnode节点，则使用它的下一个兄弟节点作为锚点元素
                anchor = prevVnode.el.nextSibling
              } else {
                // 如果没有前一个 vnode 节点，说明即将挂载的新节点是第一个子节点
                // 此时我们使用容器元素的 firstChild 作为锚点
                anchor = container.firstChild
              }
              // 挂载 newVnode
              path(null, newVnode, container, anchor)
            }
          }
        } else {
          setElementText(container, '')
          newChildren.forEach(c=> mountElement(c, container))
        }
    } else {
      if(Array.isArray(oldChildren)) {
        oldChildren.forEach(c=> unmount(c))
      } else if(typeof oldChildren === 'string'){
        setElementText(container, '')
      }
    } 
  }

  return {
    render
  }
}
const renderer = createRenderer(
  {
    //创建节点
    createElement(tag) {
      return document.createElement(tag)
    },
    //设置文本
    setElementText(el, text) {
      el.textContent = text
    },
    //创建文本节点
    createText(text) {
      el.createTextNode(text)
    },
    //插入节点
    insert(el, parent, anchor = null) {
      parent.insertBefore(el, anchor)
    },
    //挂载属性
    patchProps(el, key, prevValue, nextValue) {
      if(shouldSetAsProps(el, key)) {//DOM-property
        const {type} = typeof el[key]
        if(type === 'boolean' && nextValue === '') {
          el[key] = false
        }else {
          el[key] = nextValue
        }
      } else if(/^on/.test(key)) {//处理事件
        let invokers = el._vai || (el._vai = {})
        let invoker = invokers[key]
        const name = key.slice(2).toLowerCase()
        if(nextValue) {
          if(!invoker) {
            invoker = el._vai[key] = function(e) {
              if(Array.isArray(invoker.value)) {
                invoker.value.forEach(fn=> fn(e))
              } else {
                invoker.value(e)
              }
            }
            invoker.value = nextValue
            
            el.addEventListener(name, invoker)
          } else {
            invoker.value = nextValue
          } 
        } else if(invoker) {
          el.removeEventListener(name, invoker)
        }
      }else {
        el.setAttribute(key, nextValue)
      }
    },
    //卸载
    unmount(vnode) {
      const el = vnode.el
      const parent = el.parentNode
      if(parent) parent.removeChild(el)
    }

  }
)

renderer.render(oldVnode, document.getElementById('app'))
renderer.render(newVnode, document.getElementById('app'))
function shouldSetAsProps(el, key) {
  if(key === 'form' && el.tagName === 'INPUT') return //只读属性
  return key in el
}
</script>
</body>

</html>